<script>
import {
  computed,
  defineComponent,
  getCurrentInstance,
  nextTick,
  onMounted,
  ref,
  watch,
} from 'vue';
import FanScrollView from '../scroll-view/scroll-view.vue';

import {
  createNamespace,
  transformSize,
  transformStyleSize,
  getElementBounding,
  createUniqueId,
} from '../utils';

export default defineComponent({
  name: createNamespace('Tabs')[0],
  options: {
    // https://uniapp.dcloud.io/matter.html#mp
    virtualHost: true,
  },
  components: { FanScrollView },
  props: {
    /**
     * 数据
     */
    tabs: Array,
    /**
     * 绑定当前选中标签的标识符，可以结合 valueProp 设置成 tabs-item 中指定字段
     * @default tabs-index
     */
    active: {
      type: [Number, String],
      default: 0,
    },
    /**
     * tab-title 取值，默认直接 tabs-item
     */
    labelProp: String,
    /**
     * label/标题左右padding
     */
    labelPad: {
      type: [Number, String],
      default: 12,
    },
    /**
     * 两侧padding，默认 labelPad / 2
     */
    sidePad: [String, Number],
    /**
     * active 的值的来源
     */
    valueProp: String,
    /**
     * 滚动阈值，标签数量超过阈值且总宽度超过标签栏宽度时开始横向滚动
     */
    scrollThreshold: {
      type: Number,
      default: 4,
    },
    // 中心偏移，单位 px，当其值在 0 < x < 1 时为容器盒子宽度的占比，可以为负数
    offCentering: {
      type: Number,
      default: 0,
    },
    /**
     * 是否开启左侧收缩布局
     */
    shrink: {
      type: Boolean,
      default: false,
    },
    height: {
      type: [String, Number],
      default: 40,
    },
    bg: {
      type: String,
      default: 'white',
    },
    color: {
      type: String,
    },
    activeColor: {
      type: String,
    },
    /**
     * 选中时是否加粗放大
     */
    activeStrong: {
      type: Boolean,
      default: false,
    },
    activeStyle: [String, Object],
    textSize: {
      type: [String, Number],
    },
    /**
     * 选中内容是否可以超出盒子
     */
    activeOut: {
      type: Boolean,
      default: false,
    },
    /**
     * 是否显示指示器
     */
    indicator: {
      type: Boolean,
      default: true,
    },
    indicatorHeight: {
      type: [String, Number],
      default: 2,
    },
    indicatorWidth: {
      type: [String, Number],
      default: 'auto',
    },
    indicatorLeft: {
      type: Number,
      default: 0,
    },
    indicatorColor: String,
    indicatorStyle: [String, Object],
    /**
     * 是否显示边框，true = 'bottom'，设置为 false 则不显示
     */
    border: {
      type: [Boolean, String],
      default: true,
      validator: (value) =>
        [true, false, 'bottom', 'top', 'vertical'].includes(value),
    },
  },
  emits: ['update:active', 'change', 'click'],
  setup(props, context) {
    const uniqueId = createUniqueId({ prefix: 'fanTabs' });
    const items = computed(() => {
      return (props.tabs || []).map((a, i) => {
        return {
          ...(typeof a === 'object' ? a : null),
          label: !props.labelProp ? a : a[props.labelProp],
          value: !props.valueProp ? i : a[props.valueProp],
        };
      });
    });
    const itemRects = ref([]);
    const scrollWidth = ref(320);
    const _this = getCurrentInstance();
    let getTabsRectsNum = 0;
    const getTabsRects = async () => {
      getTabsRectsNum++;
      if (items.value.length) {
        itemRects.value = await getElementBounding({
          selector: '.fan2-tab__in',
          selectAll: true,
          scope: _this,
        });
      } else if (itemRects.value.length) itemRects.value = [];
    };
    const getRects = async () => {
      await getTabsRects();
      if (itemRects.value.length && getTabsRectsNum <= 2) {
        const item = itemRects.value.filter(
          (item) => item.height === 0 && item.width === 0,
        );
        if (!item.length) return;
        setTimeout(() => {
          getTabsRects();
        }, 300);
      }
      const scrollRect = await getElementBounding({
        selector: '#' + uniqueId,
        scope: _this,
      });
      // 支付宝小程序 width + left(padding) 才是真实的宽度，正常的 left 为 0 不影响结果
      if (scrollRect?.width)
        scrollWidth.value = scrollRect.width + (scrollRect.left || 0) * 2;
    };
    const activeIndex = computed(() => {
      const i = items.value.findIndex((a) => a.value === props.active);
      return i !== -1 ? i : 0;
    });
    const theStyle = computed(() => {
      const {
        height,
        bg,
        color,
        textSize,
        activeColor,
        labelPad,
        sidePad,
      } = props;
      const tabPad = transformSize(labelPad);
      const sPad = !sidePad ? `calc(${tabPad} / 2)` : transformSize(sidePad);
      let s = `--fan2-tabs-height:${transformSize(
        height,
      )};--fan2-tab-pad:${tabPad};--fan2-tabs-side-pad:${sPad};`;
      if (color) s += `--fan2-tabs-color:${color};`;
      if (activeColor) s += `--fan2-tabs-active-color:${activeColor};`;
      if (bg) s += `background:${bg};`;
      if (textSize) s += `font-size:${transformSize(textSize)};`;
      return s;
    });
    const theActiveStyle = computed(() =>
      transformStyleSize(props.activeStyle),
    );
    // 指示器样式
    const zsqStyle = computed(() => {
      const {
        indicator,
        indicatorHeight,
        indicatorWidth,
        indicatorColor,
        indicatorStyle,
        activeStrong,
        indicatorLeft,
      } = props;
      if (!indicator) return '';
      let s = '';
      const tab = itemRects.value[activeIndex.value];
      const { left = 6, width = 52 } = tab || {};
      s += `transform:translateX(${left + width / 2 - indicatorLeft
        }px) translateX(calc(-50%));`;
      if (indicatorWidth) {
        if (indicatorWidth === 'auto')
          s += `width:calc((${width}px - var(--fan2-tab-pad) * 2) * ${activeStrong ? 1.13 : 1
            });`;
        else
          s += `width:calc(${transformSize(indicatorWidth)} * ${activeStrong ? 1.13 : 1
            });`;
      }
      if (indicatorHeight) {
        const h = transformSize(indicatorHeight);
        s += `height:${h};top:calc(var(--fan2-tabs-height) - ${h});`;
      }
      if (indicatorColor) s += `background-color:${indicatorColor};`;

      return transformStyleSize(indicatorStyle) + s;
    });
    const borderTop = computed(() => {
      return ['top', 'vertical'].includes(props.border);
    });
    const borderBottom = computed(() => {
      return [true, 'bottom', 'vertical'].includes(props.border);
    });

    const scrollable = computed(() => {
      const length = items.value.length;
      const over = length > props.scrollThreshold;
      if (length !== itemRects.value.length) return over;
      const width = itemRects.value.reduce((sum, item) => {
        return sum + item.width;
      }, 0);
      return over && width > scrollWidth.value;
    });
    const scrollLeft = ref(0);
    const scrollToItem = (index) => {
      if (!scrollable.value) return;
      const tab = itemRects.value[index];
      if (!tab) return;
      // 支付宝小程序 rect.left 包含了 padding，左 padding = rects[0].left
      let padding = itemRects.value[0]?.left || 0;
      // #ifndef MP-ALIPAY
      padding = 0;
      // #endif
      const offset =
        Math.abs(props.offCentering) < 1
          ? scrollWidth.value * props.offCentering
          : props.offCentering;
      scrollLeft.value =
        tab.left - padding + tab.width / 2 - scrollWidth.value / 2 - offset;
    };
    watch(activeIndex, (val) => {
      scrollToItem(val);
    });

    const itemClick = (index) => {
      const item = items.value[index];
      context.emit('click', props.tabs[index], index);
      if (item.value !== props.active) {
        context.emit('update:active', item.value);
        context.emit('change', props.tabs[index], index);
      }
    };

    onMounted(() => getRects());
    watch([items, scrollable], () => {
      nextTick(getRects);
    });
    return {
      uniqueId,
      items,
      scrollable,
      scrollLeft,
      theStyle,
      theActiveStyle,
      zsqStyle,
      borderTop,
      borderBottom,
      itemClick,
    };
  },
});
</script>

<template>
  <div :id="uniqueId" :style="theStyle" class="fan2-tabs" :class="{
    'fan-hairline--top': borderTop,
    'fan-hairline--bottom': borderBottom,
    'fan2-tabs--flex': !scrollable && !shrink,
    'fan2-tabs--out': activeOut,
  }">
    <FanScrollView style="position:relative;" height="calc(100% + 17px)" :scroll-x="true" :scroll-y="activeOut"
      :scroll-left="scrollLeft" :enable-flex="!scrollable" :scrollable="scrollable" :scrollWithAnimation="true"
      :style="activeOut ? { overflow: 'visible' } : ''">
      <div class="fan2-tab__pad"></div>
      <div v-for="(item, index) in items" :key="index" :style="item.value === active ? theActiveStyle : ''"
        class="fan2-tab" :class="{
    'fan2-tab--active': item.value === active,
    'fan2-tab--strong': activeStrong,
    'fan2-tab--out': activeOut,
  }" @click="itemClick(index)">
        <div class="fan2-tab__in">
          <span v-if="!$slots.default">{{ item.label }}</span>
          <slot v-else :item="item" :index="index" :active="item.value === active"></slot>
        </div>
      </div>
      <div class="fan2-tab__pad"></div>

      <div :style="zsqStyle" class="fan2-tabs__indicator"></div>
    </FanScrollView>
  </div>
</template>

<style lang="less">
.fan2-tabs {
  --fan2-tabs-height: 40px;
  --fan2-tabs-color: #333;
  --fan2-tabs-active-color: var(--fan-primary-color);
  --fan2-tab-pad: 12px;
  --fan2-tabs-side-pad: 6px;

  height: var(--fan2-tabs-height);
  color: var(--fan2-tabs-color);
  position: relative;
  overflow: hidden;
  box-sizing: border-box;

  &--flex {
    .fan2-tab {
      flex: 1;
    }
  }

  &--out {
    overflow: visible;
  }

  &__indicator {
    position: absolute;
    z-index: 9;
    bottom: 0;
    left: 0;
    background-color: var(--fan2-tabs-active-color);
    transform: translateX(-50%);
    transition: all 0.3s linear;
    border-radius: 10px;
  }
}

.fan2-tab,
.fan2-tab__in {
  display: inline-flex;
  justify-content: center;
  align-items: center;
  box-sizing: border-box;
}

.fan2-tab {
  height: var(--fan2-tabs-height);
  overflow: hidden;
  color: var(--fan2-tabs-color);
  white-space: nowrap;
  text-overflow: ellipsis;
  flex: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;

  &__pad {
    display: inline-flex;
    flex: none;
    width: var(--fan2-tabs-side-pad);
  }

  &__in {
    padding: 0 var(--fan2-tab-pad);
  }

  &--active {
    color: var(--fan2-tabs-active-color);

    &.fan2-tab--strong {
      font-weight: bold;
      font-size: 1.13em;
      transition: textSize 0.3s linear;
    }
  }

  &--out {
    overflow: visible;
  }
}
</style>
